﻿#define _CRT_SECURE_NO_WARNINGS 1

//异常

// 程序出现问题时，我们通过抛出(throw)⼀个对象来引发⼀个异常
//throw执行后 throw后面的语句将不再执行 去寻找匹配的catch块
/*
⾸先检查throw本⾝是否在try块内部，如果在则查找匹配的catch语句，
如果有匹配的，则跳到catch的地⽅进⾏处理。
如果当前函数中没有try/catch⼦句，或者有try/catch⼦句但是类型不匹配，则退出当前函数，继续
在外层调⽤函数链中查找，上述查找的catch过程被称为栈展开。
*/

//找catch首先要求类型匹配 其次要求最近
#include <iostream>
using namespace std;

/*，函数参数列表后⾯加noexcept表⽰不会抛出异常，啥都不加表⽰可能会抛出异常。*/
/*编译器并不会在编译时检查noexcept，也就是说如果⼀个函数⽤noexcept修饰了，但是同时⼜包
含了throw语句或者调⽤的函数可能会抛出异常，编译器还是会顺利编译通过的(有些编译器可能会
报个警告)。但是⼀个声明了noexcept的函数抛出了异常，程序会调⽤terminate终止程序*/
double Divide(int a, int b) noexcept
{
	try
	{
		// 当b == 0时抛出异常
		if (b == 0)
		{
			string s("Divide by zero condition!");
			throw s; //抛出一个字符串对象
		}
		else
		{
			return ((double)a / (double)b);
		}
	}
	catch (int errid)
	{
		cout << errid << endl;
	}
}

//void Func()
//{
//	int len, time;
//	cin >> len >> time;
//	try
//	{
//		cout << Divide(len, time) << endl;
//	}
//	catch (const char* errmsg)
//	{
//		cout << errmsg << endl;
//	}
//	cout << __FUNCTION__ << ":" << __LINE__ << "行执行" << endl;
//}

//int main()
//{
//	while (1)
//	{
//		try
//		{
//		/*	int x = 0;
//			cout << x << endl;*/
//			Func();
//		}
//		catch (const string& errmsg)
//		{
//			cout << errmsg << endl;
//			// 记录项目
//		}
//	}
//
//	return 0;
//}


#include<thread>

// 一般大型项目程序才会使用异常，下面我们模拟设计一个服务的几个模块
// 每个模块的继承都是Exception的派生类，每个模块可以添加自己的数据
// 最后捕获时，我们捕获基类就可以
class Exception
{
public:
	Exception(const string& errmsg, int id)
		:_errmsg(errmsg)
		, _id(id)
	{
	}

	virtual string what() const //设计为虚函数 用于实现多态
	{
		return _errmsg;
	}

	int getid() const
	{
		return _id;
	}

protected:
	string _errmsg;
	int _id;
};

class SqlException : public Exception  //sql语句异常
{
public:
	SqlException(const string& errmsg, int id, const string& sql)
		:Exception(errmsg, id)
		, _sql(sql)
	{
	}

	virtual string what() const
	{
		string str = "SqlException:";
		str += _errmsg;
		str += "->";
		str += _sql;
		return str;
	}
private:
	const string _sql;
};

class CacheException : public Exception  //缓存异常
{
public:
	CacheException(const string& errmsg, int id)
		:Exception(errmsg, id)
	{
	}

	virtual string what() const
	{
		string str = "CacheException:";
		str += _errmsg;
		return str;
	}
};

class HttpException : public Exception //协议异常
{
public:
	HttpException(const string& errmsg, int id, const string& type)
		:Exception(errmsg, id)
		, _type(type)
	{
	}

	virtual string what() const
	{
		string str = "HttpException:";
		str += _type;
		str += ":";
		str += _errmsg;
		return str;
	}
private:
	const string _type;
};

void SQLMgr()
{
	if (rand() % 7 == 0)
	{
		throw SqlException("权限不足", 100, "select * from name = '张三'");
	}
	else
	{
		cout << "SQLMgr 调用成功" << endl;
	}
}

void CacheMgr()
{
	if (rand() % 5 == 0)
	{
		throw CacheException("权限不足", 100);
	}
	else if (rand() % 6 == 0)
	{
		throw CacheException("数据不存在", 101);
	}
	else
	{
		cout << "CacheMgr 调用成功" << endl;
	}
	SQLMgr();
}

void HttpServer() //、先看协议层 再看缓存层 再看sql层
{
	if (rand() % 3 == 0)
	{
		throw HttpException("请求资源不存在", 100, "get");
	}
	else if (rand() % 4 == 0)
	{
		throw HttpException("权限不足", 101, "post");
	}
	else
	{
		cout << "HttpServer调用成功" << endl;
	}
	CacheMgr();
}


//int main()
//{
//	srand(time(0));
//	while (1)
//	{
//		this_thread::sleep_for(chrono::seconds(1)); //休眠一秒
//
//		try
//		{
//			HttpServer();
//		}
//		//一般先捕获特定异常 再捕获基类异常 再捕获所有异常
//		catch (const Exception& e) // 这里捕获基类，基类对象和派生类对象都可以被捕获
//		{
//			// 多态
//			cout << e.what() << endl;
//			// 记录异常日志
//		}
//		catch (...) //使用这个可以捕获所有异常 但不知道异常来源
//		{
//			cout << "未知异常" << endl;
//		}
//	}
//	return 0;
//}


// 下面程序模拟展示了聊天时发送消息，发送失败补货异常，但是可能在
// 电梯地下室等场景手机信号不好，则需要多次尝试，如果多次尝试都发
// 送不出去，则就需要捕获异常再重新抛出，其次如果不是网络差导致的
// 错误，捕获后也要重新抛出。
void _SeedMsg(const string& s)
{
	if (rand() % 3 == 0)
	{
		throw HttpException("网络不稳定，发送失败", 102, "put"); //注意这里不同的异常号就可以帮助区分异常原因
	}
	else if (rand() % 7 == 0)
	{
		throw HttpException("你已经不是对象的好友，发送失败", 103, "put");
	}
	else
	{
		cout << "发送成功" << endl;
	}
}

void SendMsg(const string& s)
{
	// 发送消息失败，则再重试3次
	for (size_t i = 0; i < 4; i++)
	{
		try
		{
			_SeedMsg(s);
			break;
		}
		/*
		有时catch到⼀个异常对象后，需要对错误进⾏分类，其中的某种异常错误需要进⾏特殊的处理，其他
		错误则重新抛出异常给外层调⽤链处理。捕获异常后需要重新抛出，直接throw;
		就可以把捕获的对象直接抛出。
		*/
		catch (const Exception& e)
		{
			// 捕获异常，if中是102号错误，网络不稳定，则重新发送
			// 捕获异常，else中不是102号错误，则将异常重新抛出
			if (e.getid() == 102)
			{
				// 重试三次以后否失败了，则说明网络太差了，重新抛出异常
				if (i == 3)
					throw; //这种写法是重新抛出当前正在处理的异常

				cout << "开始第" << i + 1 << "重试" << endl;
			}
			else
			{
				throw e;//如果这样写的话这里抛出的是异常对象的拷贝 因为e在出了作用域后销毁 编译器会尽量优化为移动构造 降低代价
			}
		}
	}
}

//int main()
//{
//	srand(time(0));
//	string str;
//	while (cin >> str)
//	{
//		try
//		{
//			SendMsg(str);
//		}
//		catch (const Exception& e)
//		{
//			cout << e.what() << endl << endl;
//		}
//		catch (...)
//		{
//			cout << "Unkown Exception" << endl;
//		}
//	}
//
//	return 0;
//}


//但是异常也会带来安全问题
/*
异常抛出后，后⾯的代码就不再执⾏，前⾯申请了资源(内存、锁等)，后⾯进⾏释放，但是中间可
能会抛异常就会导致资源没有释放，这⾥由于异常就引发了资源泄漏，产⽣安全性的问题。中间我
们需要捕获异常，释放资源后⾯再重新抛出
*/

/*
其次析构函数中，如果抛出异常也要谨慎处理，⽐如析构函数要释放10个资源，释放到第5个时抛
出异常，则也需要捕获处理，否则后⾯的5个资源就没释放，也资源泄漏了
*/

void Func()
{
	// 这里可以看到如果发生除0错误抛出异常，另外下面的array没有得到释放。
	// 所以这里捕获异常后并不处理异常，异常还是交给外层处理，这里捕获了再
	// 重新抛出去。
	int* array = new int[10];
	try
	{
		int len, time;
		cin >> len >> time;
		cout << Divide(len, time) << endl;
	}
	catch (...)  //加上这个 一旦发生异常 先释放资源 再重新抛出
	{
		cout << "delete []" << array << endl;
		delete[] array;

		throw;
	}

	cout << "delete []" << array << endl;
	delete[] array;
}


int main()
{
	int i = 0;
	/*
	noexcept(expression)还可以作为⼀个运算符去检测⼀个表达式是否会抛出异常，可能会则返回false，不会就返回true
	*/
	cout << noexcept(Divide(1, 2)) << endl; //0  不加noexcept时
	cout << noexcept(Divide(1, 0)) << endl; //0  不加noexcept时
	cout << noexcept(++i) << endl; //1

	try
	{
		Func();
	}
	//这样三层的catch 结构更加清晰
	catch (const string& errmsg)
	{
		cout << errmsg << endl;
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	catch (...)
	{
		cout << "Unkown Exception" << endl;
	}
	return 0;
}
